import { intervalToDuration, addSeconds, formatISODuration } from 'date-fns';

function pick(setting) {
  const isObject = some => typeof some === 'object' && some !== null;
  const isArray = some => some instanceof Array;
  const isString = some => typeof some === 'string';

  function typeConvert(value, typeString, ...typeParams) {
    if (!typeString) return value;
    switch (typeString.trim().toLowerCase()) {
      case 'float':
        return parseFloat(value, ...typeParams);
      case 'int':
        return parseInt(value, ...typeParams);
      case 'date-iso': {
        const d = new Date(value);
        return Number.isNaN(d.getTime()) ? '' : d.toISOString();
      }
      case 'duration-iso-8601': {
        const duration = parseInt(value, 10);
        const start = new Date();
        const end = addSeconds(start, Number.isNaN(duration) ? 0 : duration);
        return formatISODuration(intervalToDuration({ start, end }));
      }
      default:
        return value;
    }
  }

  function deepProp(object, string) {
    const [propKey, propType, ...propTypeParams] = string.split(',');
    const props = propKey.trim().split('.');
    const value = props.reduce(
      (o, key) => (o && isObject(o) ? o[key] : o),
      object
    );
    return {
      key: props[props.length - 1],
      value: typeConvert(value, propType, ...propTypeParams)
    };
  }

  function pickArray(array) {
    return function pickArrayFunc(object) {
      return array.reduce((o, k) => {
        const { key, value } = deepProp(object, k);
        return { ...o, [key]: value };
      }, {});
    };
  }

  function pickObject(objectSetting) {
    return function pickObjectFunc(object) {
      return Object.keys(objectSetting).reduce((o, k) => {
        const { value } = deepProp(object, objectSetting[k]);
        return { ...o, [k]: value };
      }, {});
    };
  }

  if (isArray(setting)) {
    return pickArray(setting);
  }
  if (isObject(setting)) {
    return pickObject(setting);
  }
  if (isString(setting)) {
    return obj => deepProp(obj, setting).value;
  }

  throw new Error('参数必须为String、Object、Array类型');
}

function pickValue(setting) {
  const pickProps = pick(setting);
  return function getValues(data) {
    const obj = pickProps(data);
    return Object.keys(obj).map(k => obj[k]);
  };
}

function memo(fn) {
  let result = null;
  let isPromise = false;
  let executed = false;

  if (typeof fn !== 'function') {
    throw new Error('fn must be a function');
  }

  return function memoFunc(...rest) {
    if (executed) {
      return isPromise ? Promise.resolve(...result) : result;
    }

    const res = fn(...rest);
    isPromise = typeof res === 'object' && typeof res.then === 'function';

    if (isPromise) {
      return new Promise((resolve, reject) => {
        res
          .then((...final) => {
            result = final;
            executed = true;
            resolve(...final);
          })
          .catch(e => {
            reject(e);
          });
      });
    } else {
      result = res;
      executed = true;
      return res;
    }
  };
}

function getRandomStream() {
  const s = Math.random().toString(16).slice(2).toUpperCase();
  let i = 0;
  const next = () => {
    i += 1;
    return i < s.length ? { value: s.charAt(i), next } : getRandomStream();
  };
  return { value: s.charAt(i), next };
}

function noLeadingNumber(c, pos) {
  return !(pos === 0 && /[0-9]/.test(c));
}

function getRandomId(len, filter = noLeadingNumber) {
  const getChars = (pos, stream, s) => {
    if (pos >= len) return s;
    return filter(stream.value, pos)
      ? getChars(pos + 1, stream.next(), s + stream.value)
      : getChars(pos, stream.next(), s);
  };
  return getChars(0, getRandomStream(), '');
}

function getCallbackName(namespace) {
  return `${namespace.toString()}_CALLBACK_${getRandomId(16)}`;
}

function excludeKeys(object, keys) {
  return Object.keys(object).reduce(
    (o, k) => (keys.includes(k) ? o : { ...o, [k]: object[k] }),
    {}
  );
}

function isInApp() {
  if (!process.client) return false;
  if (window.webkit) {
    try {
      return !!window.webkit.messageHandlers.moreShareOption;
    } catch (e) {
      return false;
    }
  } else if (window.LifeDetailInterface) {
    return true;
  }
  return false;
}

const memoIsInApp = memo(isInApp);

function getServerUserAgentLanguage(req) {
  return req.headers['accept-language'] || '';
}

function getClientUserAgentLanguage() {
  if (typeof window === 'undefined') return '';
  return window.navigator.language;
}

function getUserAgentLanguage(...rest) {
  if (process.client) {
    return getClientUserAgentLanguage(...rest);
  }
  return getServerUserAgentLanguage(...rest);
}

function range(start, end) {
  const r = (s, e, l) => {
    if (s >= e) return l;
    l.push(s);
    return r(s + 1, e, l);
  };
  return r(start, end, []);
}

function shuffleList(list) {
  const idList = range(0, list.length);
  const randomIdList = [];
  while (idList.length) {
    const index = Math.floor(Math.random() * idList.length);
    randomIdList.push(idList[index]);
    idList.splice(index, 1);
  }
  return randomIdList.map(id => list[id]);
}

function onlyNumber(str) {
  return `${str}`
    .split('')
    .filter(c => /[0-9]/.test(c))
    .join('');
}

function getDeviceType() {
  if (!process.client) return 0;
  const ua = window.navigator.userAgent.toLowerCase();
  if (ua.match(/iphone/i)) {
    return 2;
  }
  if (ua.match(/android/i)) {
    return 1;
  }
  return 0;
}
//判断是否在android终端
function isInAndroid() {
  const u = window.navigator.userAgent;
  return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
}

function convertLanguageToLang(language) {
  const [lang] = language.split(',');
  const lanMap = {
    zh: 'zh-Hans',
    km: 'km-KH',
    en: 'en-US'
  };
  const key = Object.keys(lanMap).find(k => {
    const reg = new RegExp(k, 'i');
    return reg.test(lang);
  });
  return key ? lanMap[key] : 'km-KH';
}

function getClientUserAgentLang() {
  const language = getClientUserAgentLanguage();
  return convertLanguageToLang(language);
}

function parseQuery(str) {
  if (typeof str !== 'string') return {};
  const pairSeq = str.slice(str.indexOf('?') + 1);
  return pairSeq.split('&').reduce((o, pairStr) => {
    const [key, value] = pairStr.split('=');
    if (key && value) return { ...o, [key]: value };
    return o;
  }, {});
}

function tryGetDeviceType() {
  if (typeof window == 'undefined') return 0;
  const queryObject = parseQuery(window.location.search);
  const deviceType = queryObject.os;
  const intDeviceType = parseInt(deviceType, 10);
  return Number.isNaN(intDeviceType) ? getDeviceType() : intDeviceType;
}

function tryGetAppVersion() {
  if (typeof window == 'undefined') return '';
  const queryObject = parseQuery(window.location.href);
  return queryObject.app_version || '';
}

function tryGetIntegralAppVersion() {
  const version = tryGetAppVersion();
  if (!version) return 0;
  const intVersion = parseInt(version.replace(/\./g, ''), 10);
  return Number.isNaN(intVersion) ? 0 : intVersion;
}

const memoTryGetDeviceType = memo(tryGetDeviceType);
const memoTryGetAppVersion = memo(tryGetAppVersion);
const memoTryGetIntegralAppVersion = memo(tryGetIntegralAppVersion);

// steps: [low base，high base];
function convertNToBase(s, bases = []) {
  if (!Array.isArray(bases)) {
    throw new Error(`bases must be type of Array, got ${typeof bases}`);
  }
  const reversedBases = bases
    .reduce((list, multi) => [...list, list[list.length - 1] * multi], [1])
    .reverse();
  const convert = (n, bases, result) => {
    if (bases.length <= 0) return result;
    const base = bases.shift();
    return convert(n % base, bases, [...result, Math.floor(n / base)]);
  };
  return convert(s, reversedBases, []);
}

function paddingZero(n) {
  const negative = n < 0;
  const positiveN = n < 0 ? -n : n;
  const paddedN = positiveN >= 10 ? `${n}` : `0${n}`;
  return negative ? `-${paddedN}` : paddedN;
}

function filterLeadingZero(list) {
  if (list.length === 0) return list;
  if (list[0]) return list;
  return filterLeadingZero(list.slice(1));
}

function convertSecondsToReadableHMS(s) {
  return convertNToBase(s, [60, 60]).map(paddingZero);
}

function convertSecondsToReadableMS(s) {
  return convertNToBase(s, [60]).map(paddingZero);
}

export {
  pick,
  pickValue,
  getRandomId,
  excludeKeys,
  isInApp,
  memoIsInApp,
  getClientUserAgentLang,
  getUserAgentLanguage,
  getDeviceType,
  tryGetAppVersion,
  tryGetDeviceType,
  memoTryGetAppVersion,
  memoTryGetDeviceType,
  memoTryGetIntegralAppVersion,
  isInAndroid,
  shuffleList,
  onlyNumber,
  convertSecondsToReadableHMS,
  convertSecondsToReadableMS
};
